package com.cdream.common.lock.aspect;

import com.cdream.common.lock.annotation.Lock;
import com.cdream.common.lock.autoconfig.LockProperties;
import com.cdream.common.lock.core.LockFailureStrategy;
import com.cdream.common.lock.core.LockInfo;
import com.cdream.common.lock.core.LockKeyBuilder;
import com.cdream.common.lock.core.LockTemplate;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;

@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class LockAspect {
    private final LockTemplate lockTemplate;
    private final LockKeyBuilder lockKeyBuilder;
    private final LockFailureStrategy lockFailureStrategy;

    private final LockProperties lockProperties;

    public LockAspect(LockTemplate lockTemplate, LockKeyBuilder lockKeyBuilder, LockFailureStrategy lockFailureStrategy, LockProperties properties) {
        this.lockTemplate = lockTemplate;
        this.lockKeyBuilder = lockKeyBuilder;
        this.lockFailureStrategy = lockFailureStrategy;
        this.lockProperties = properties;
    }

    @Pointcut("@annotation(com.cdream.common.lock.annotation.Lock)")
    public void lockPointCut() {
    }


    @Around("lockPointCut()")
    public Object distributeLock(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();
        Lock lock = method.getAnnotation(Lock.class);
        LockInfo lockInfo = null;
        try {
            String prefix = lockProperties.getLockKeyPrefix() + ":";
            prefix += StringUtils.hasText(lock.name()) ? lock.name() :
                    method.getDeclaringClass().getName() + method.getName();
            String key = prefix + "#" + lockKeyBuilder.buildKey(joinPoint, lock.keys());
            lockInfo = lockTemplate.lock(key, lock.expire(), lock.acquireTimeout(), lock.executor());
            if (null != lockInfo) {
                return joinPoint.proceed();
            }
            // lock failure
            lockFailureStrategy.onLockFailure(key, method, args);
            return null;
        } finally {
            if (null != lockInfo && lock.autoRelease()) {
                final boolean releaseLock = lockTemplate.releaseLock(lockInfo);
                if (!releaseLock) {
                    log.error("releaseLock fail,lockKey={},lockValue={}", lockInfo.getLockKey(), lockInfo.getLockValue());
                }
            }
        }

    }
}
